iT邦幫忙

2024 iThome 鐵人賽

DAY 11
0
佛心分享-IT 人自學之術

後端小白自學 Laravel系列 第 11

第 11 天:用戶認證

  • 分享至 

  • xImage
  •  

Laravel 的認證系統概述


Laravel 提供了一個功能全面的認證系統,用於處理用戶註冊、登錄和密碼重置等功能。
它內建了一些實用的功能和工具,使得用戶認證的實現變得更簡單和高效。

主要特點:

  1. 簡單的用戶認證:提供了現成的註冊、登錄、密碼重置等功能。
  2. 用戶模型:通常與 Laravel 的用戶模型(User 模型)進行配合,支持各種認證操作。
  3. 中間件:使用中間件來控制訪問權限,例如,auth 中間件可以限制只有經過認證的用戶才能訪問某些路由。

使用 Laravel 內置的認證系統 Auth


在 Laravel 8.x 及以後版本中,make:auth 命令不再直接支持。因此,需要安裝 Laravel Breeze 或 Laravel Jetstream 來添加認證系統。
如果不想安裝套件也可以使用 Laravel 的 Auth facade ,他提供了一個簡單的接口來訪問身份驗證服務,允許開發進行用戶認證、登錄、登出、檢查當前用戶等操作。
以下是如何使用 Auth facade 來管理和存取身份驗證服務的詳細說明:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    /**
     * 處理身份驗證嘗試
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function authenticate(Request $request)
    {
        // 驗證用戶提交的數據
        $credentials = $request->validate([
            'email' => ['required', 'email'],
            'password' => ['required'],
        ]);

        /**
         * 用提供的憑證(email 和 password)去資料庫比對
         * 通過後會重新生成用戶的會話 ID,以防止會話劫持
         */
        if (Auth::attempt($credentials)) {
            $request->session()->regenerate();

            return redirect()->intended('dashboard');
        }

        // 如果身份驗證失敗,畫面會出現錯誤訊息
        return back()->withErrors([
            'email' => 'The provided credentials do not match our records.',
        ]);
    }
}

使用 Auth Facade 的基本操作

  1. 確認用戶是否已經登錄 Auth::check():如果用戶已經登錄,這個方法會返回 true,否則返回 false。
     use Illuminate\Support\Facades\Auth;
    
     if (Auth::check()) {
         // 用戶已經登錄
         $user = Auth::user(); // 獲取當前登錄的用戶
     } else {
         // 用戶尚未登錄
     }
    
  2. 獲取當前登錄的用戶 Auth::user():這個方法返回一個 User 模型實例。
     use Illuminate\Support\Facades\Auth;
    
     $user = Auth::user();
    
     if ($user) {
         // 輸出用戶名稱
         echo $user->name;
     }
    
  3. 登錄用戶 Auth::attempt():接受一個包含用戶憑證的陣列(例如,電子郵件和密碼),並返回布林值表示是否成功登錄。
     use Illuminate\Support\Facades\Auth;
    
     $credentials = [
         'email' => 'user@example.com',
         'password' => 'password123'
     ];
    
     if (Auth::attempt($credentials)) {
         // 登錄成功
         return redirect()->intended('dashboard');
     } else {
         // 登錄失敗
         return redirect()->back()->withErrors([
             'email' => 'The provided credentials do not match our records.',
         ]);
     }
    
  4. 登出用戶 Auth::logout():登出當前登錄的用戶。
     use Illuminate\Support\Facades\Auth;
    
     Auth::logout();
    
     // 重定向到首頁
     return redirect('/');
    
  5. 設置登錄用戶 Auth::login():手動登錄用戶,通常在測試環境中使用。
    use Illuminate\Support\Facades\Auth;
    use App\Models\User;
    
    $user = User::find(1); // 找到用戶
    
    Auth::login($user); // 登錄用戶
    
  6. 設置自定義指定守衛 Auth::guard():Laravel 支持多種認證守衛(guards),可以在 config/auth.php 文件中配置。
    use Illuminate\Support\Facades\Auth;
    
    $admin = Auth::guard('admin')->user();
    
  7. 驗證用戶角色或權限:在控制器或中間件中檢查用戶角色或權限
    use Illuminate\Support\Facades\Auth;
    
    if (Auth::check() && Auth::user()->hasRole('admin')) {
        // 用戶是管理員
    }
    

執行數據遷移
安裝認證系統後,運行遷移命令 php artisan migrate 來創建必要的數據表

註冊、登錄、密碼重置功能的實現


參考資料:Laravel 9.x 驗證

Step 1:建立資料庫連線,設定 .env 檔案
安裝 Laravel 並配置好資料庫連線,通常是在 .env 檔案中設定

DB_CONNECTION=sqlite      //使用的資料庫,範例中是使用mysql
DB_HOST=127.0.0.1         //資料庫若是再別的主機再另行設定
DB_PORT=3306              //資料庫若是再別的主機再另行設定
DB_DATABASE=''            //資料庫名稱

🔔 若是使用 php artisan serve 開發,修改 .env 後必須重開 php artisan serve

Step 2:建立認證相關的表
下指令 php artisan make:migration register_list 後,會新增一個檔案 database\migrations\2024_08_14_143812_register_list.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        // 建立新的名為 register_list 的 table
        Schema::create('register_list', function (Blueprint $table) {
        
            // 建立 column 主鍵(primary key),並且為自動遞增
            $table->id();
            
            // 建立名為 email 的 column,只能是唯一
            $table->string('email')->unique();
            
            // 建立名為 email_verified_at 的 column,並且允許為空值
            $table->timestamp('email_verified_at')->nullable();
            
            // 建立名為 password 的column,type 為字串
            $table->string('password');
            
            // 建立名為 remember_token 記住我的 column
            $table->rememberToken();
            
            // 建立 create_at、update_at 的 column,type 為date。create_at 會在新增時自動紀錄時間,update_at 會再更新時自動紀錄時間
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        //
    }
};

Step 3:建立 table
下指令 php artisan migrate laravel 就會自動將 table 建立好
自動生成的表格

Step 4:建立 model
MVC 結構中的 "M",也就是 model,基本上 model 會對應到資料庫,每個 model 對應一個 table,下指令 php artisan make:model Users,就會產生一個名為 Users 空的模型,再放上資料庫中的 table 名稱,還設定的 column

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Users extends Model
{
    use HasFactory;

    protected $table = 'users';  //DB 中的 table 名稱
    public $primaryKey = 'id';
    protected $fillable = ['username', 'password', 'email', 'remember_token'];  //白名單,就是可以修改的欄位
    protected $hidden = [
        'password', 'remember_token',
    ];  //可以隱藏的欄位
}

參考文章:Day13 laravel 使用 model 下篇

Step 5:設定註冊和登入控制器,並且使用 model
用戶可以通過提供必要的資料(如電子郵件和密碼)來註冊新帳戶。

use App\Models\Users;
use Illuminate\Support\Facades\Hash;
use Illuminate\Http\Request;

class RegisterController extends Controller
{
    public function register(Request $request)
    {
        // 驗證請求
        $validatedData = $request->validate([
            'email' => ['required', 'email|unique:users,email'],
            'password' => ['required, min:8', 'confirmed'],
        ]);

        // 建立使用者
        $user = Users::create([
            'email' => $validatedData['email'],
            'password' => Hash::make($validatedData['password']),
        ]);

        // 你可以在這裡選擇傳送驗證郵件

        return redirect()->route('login')->with('success', '註冊成功!請登入');
    }
}

用戶可以通過電子郵件和密碼登錄。

use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    public function login(Request $request)
    {
        // 驗證請求
        $validatedData = $request->validate([
            'email' => 'required|email',
            'password' => 'required',
        ]);

        // 嘗試登入
        if (Auth::attempt(['email' => $validatedData['email'], 'password' => $validatedData['password']])) {
            return redirect()->intended('home')->with('success', '登入成功!');
        }

        return redirect()->back()->withErrors(['email' => '電子信箱或密碼不正確']);
    }
}

Step 6:建立 view.bland.php

<form method="POST" action="{{ route('register') }}">
    @csrf
    <label for="email">電子信箱:</label>
    <input type="email" name="email" required>
    
    <label for="password">密碼:</label>
    <input type="password" name="password" required>
    
    <label for="password_confirmation">確認密碼:</label>
    <input type="password" name="password_confirmation" required>
    
    <button type="submit">註冊</button>
</form>

Step 7:設定註冊路由
routes/web.php 會包含對應的路由,app/Http/Controllers/Auth/RegisterController.php 處理註冊邏輯。

use App\Http\Controllers\Auth\RegisterController;
use App\Http\Controllers\Auth\LoginController;

Route::get('register', function () {
    return view('register');
})->name('register');

Route::post('register', [RegisterController::class, 'register']);

Route::get('login', function () {
    return view('login');
})->name('login');

Route::post('login', [LoginController::class, 'login']);

✍🏻 每週任務


用 laravel 內建的認證系統 auth,嘗試驗證使用者提供給前端呼叫 api 的資料
建立連線資料庫

  1. 建立了用戶模型 php artisan make:model RegisterUsers 和資料表 php artisan make:migration create_register_users_table
  2. 在遷移文件 2024_08_19_135051_create_register_users_table.php 中,添加基本的用戶欄位
    <?php
    
    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Support\Facades\Schema;
    
    return new class extends Migration {
        /**
         * Run the migrations.
         */
        public function up(): void
        {
            // 建立新的名為 register_list 的 table
            Schema::create('register_users', function (Blueprint $table) {
                // 建立 column 主鍵(primary key),並且為自動遞增
                $table->id();
    
                // 建立名為 email 的 column,只能是唯一
                $table->string('email')->unique();
    
                // 建立名為 email_verified_at 的 column,並且允許為空值
                $table->timestamp('email_verified_at')->nullable();
    
                // 建立名為 password 的column,type 為字串
                $table->string('password');
    
                // 建立名為 remember_token 記住我的 column
                $table->rememberToken();
    
                // 建立 create_at、update_at 的 column,type 為date。create_at 會在新增時自動紀錄時間,update_at 會再更新時自動紀錄時間
                $table->timestamps();
            });
        }
    
        /**
         * Reverse the migrations.
         */
        public function down(): void
        {
            //
        }
      };
    
  3. 運行遷移 php artisan migrate

建立 API 路由
web.phpapi.php 本身有差異,所以 api 放哪裡變成很重要,練習過程我發現兩邊要顧到真的太難還會弄錯,所以最後決定在 web.php 練習

🐘 補充說明:
如果今天要跟一個只會 JavaScript 的後端新手解釋 web.phpapi.php 兩個差異,我覺得這樣比較好懂:
web.php -> 在瀏覽器上可以看到的內容,通常只能使用 GET 和 POST
api.php -> 在 postman 打才能看到的內容,所有的 Restful API 都可以使用

<?php

use App\Http\Controllers\AuthController;
use Illuminate\Support\Facades\Route;

// 註冊
Route::get('/', [AuthController::class, 'register'])->name('register');
Route::post('/register', [AuthController::class, 'handelRegister']);

// 登入
Route::get('login', [AuthController::class, 'login'])->name('login');
Route::post('login', [AuthController::class, 'handelLogin']);

// 首頁
Route::get('/home', [AuthController::class, 'index'])->name('home');

// 登出
Route::post('/logout', [AuthController::class, 'logout'])->name('logout');

建立控制器驗證 API 請求

php artisan make:controller AuthController
<?php

namespace App\Http\Controllers;

use App\Models\RegisterUsers;
use Illuminate\Contracts\View\View;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use MongoDB\Driver\Session;


class AuthController extends Controller
{
    /**
     * 進入註冊畫面
     *
     * @return View
     */
    public function register(): View
    {
        return view('register');
    }

    /**
     * 建立使用者資訊到資料庫
     *
     * @param Request $request
     * @return string
     */
    public function handelRegister(Request $request): string
    {
        $validatedData = $request->validate([
           'email'    => 'required|email|unique:register_users|max:70',
           'password' => 'required|min:2|max:16'
        ]);

        // 建立使用者
        $user = RegisterUsers::query()->create([
                                                   'email'    => $validatedData['email'],
                                                   'password' => Hash::make($validatedData['password'])
                                               ]);

        return redirect()->route('login')->with('success', '註冊成功!請登入');
    }

    /**
     * 進入登入畫面
     *
     * @return View
     */
    public function login(): View
    {
        return view('login');
    }

    /**
     * 處理登入驗證(規劃 - 成功跳轉頁面,失敗轉回登入頁面)
     *
     * @param Request $request
     * @return string
     */
    public function handelLogin(Request $request): string
    {
        try {
            $validatedData = $request->validate([
               'email'    => 'required|email','password' => 'required'
            ]);
        } catch (\Throwable $exception) {
            damp($exception->getMessage());
            $validatedData = ['email' => 'aaa@test.net', 'password' => 'xxx'];
        }

        if (Auth::attempt($validatedData, true)) {
            $request->session()->regenerate();

            return redirect()->intended('home');
        }
        return redirect()->route('login')->withErrors(['email' => '電子信箱或密碼不正確']);
    }

    /**
     * 進入首頁畫面
     *
     * @return View
     */
    public function index(): View
    {
        return view('welcome');
    }

    /**
     * 登出
     *
     * @param Request $request
     * @return RedirectResponse
     */
    public function logout(Request $request): RedirectResponse
    {
        Auth::logout();

        $request->session()->invalidate();

        $request->session()->regenerateToken();

        return redirect()->route('login')->withErrors(['logout' => '你已經登出'])->withInput();
    }

因為模型是自建的,所以需要設定服務提供者
🐘 補充說明:(參考內容)
在 Laravel 的核心中,身份驗證工具是由「守衛」和「提供者」所組成。
守衛定義了在每個請求中(file path: config/auth.php),如何與使用者進行身份驗證。
舉例來說,Laravel 內建的一個 session 守衛會使用 session 儲存器和 cookies 來維護驗證狀態。然後另一個 token 守衛會使用每個請求所傳遞的「API token」來認證使用者。

建立 blade.php

  1. 註冊頁面

    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport"
           content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>register</title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js">      </script>
        <style>
            label {
                font-weight: 600;
                color: #666;
            }
            .box8{
                box-shadow: 0px 0px 5px 1px #999;
            }
            .mx-t3{
                margin-top: -3rem;
            }
        </style>
    </head>
    <body>
    <div class="container mt-3">
        <form method="post" action="/register">
            @csrf
            <div class="row jumbotron box8">
                <div class="col-sm-12 mx-t3 mb-4">
                    <h2 class="text-center text-info">Register</h2>
                </div>
                <div class="col-sm-12 form-group">
                    <label for="email">Email</label>
                    <input type="email" class="form-control" name="email" id="email" placeholder="Enter your email."
                        required>
                </div>
                <div class="col-sm-12 form-group">
                    <label for="pass">Password</label>
                    <input type="Password" name="password" class="form-control" id="pass" placeholder="Enter your password."
                        required>
                </div>
                <div class="col-sm-12 form-group">
                    <label for="pass2">Confirm Password</label>
                    <input type="Password" name="cnf-password" class="form-control" id="pass2"
                        placeholder="Re-enter your password." required>
                </div>
                <div class="col-sm-12 form-group mb-0">
                    <button class="btn btn-primary float-right">Submit</button>
                </div>
            </div>
        </form>
    </div>
    </body>
    </html>
    
  2. 登入畫面

    <!doctype html>
    <html lang="en">
       <head>
        <meta charset="UTF-8">
        <meta name="viewport"
           content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Login</title>
        <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css" rel="stylesheet">
        <style>
            #login img {
                margin: 10px 0;
            }
    
            #login .center {
                text-align: center;
            }
    
            #login .login {
                max-width: 300px;
                margin: 35px auto;
            }
    
            #login .login-form {
                padding: 0px 25px;
            }
        </style>
    </head>
    <body>
    <div id="login" class="container">
        <div class="row-fluid">
            <div class="span12">
                <div class="login well well-small">
                    <div class="center">
                        <img src="http://placehold.it/250x100&text=Logo" alt="logo">
                    </div>
                    @if ($errors->any())
                        <div class="alert alert-danger">
                            <ul style="margin-bottom: 0">
                                @foreach ($errors->all() as $error)
                                    <li>{{ $error }}</li>
                                @endforeach
                            </ul>
                        </div>
                    @endif
                    <form method="POST" action="/login" style="" class="login-form" id="UserLoginForm"
                       accept-charset="utf-8">
                        @csrf
                        <div class="control-group">
                            <div class="input-prepend">
                                <span class="add-on"><i class="icon-user"></i></span>
                                <input type="email" class="form-control" name="email" id="email" placeholder="Enter your email."
                                    required>
                            </div>
                        </div>
                        <div class="control-group">
                            <div class="input-prepend">
                                <span class="add-on"><i class="icon-lock"></i></span>
                                <input type="Password" name="password" class="form-control" id="pass" placeholder="Enter your password."
                                    required>
                            </div>
                        </div>
                        <div class="control-group">
                            <label id="remember-me" style="display: flex; align-items: start" for="remember">
                                <input type="checkbox" style="margin-right: 10px;" name="remember" value="1" id="UserRememberMe">
                             Remember Me?</label>
                        </div>
                        <div class="control-group">
                            <input class="btn btn-primary btn-large btn-block" type="submit" value="Sign in">
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
    </body>
    </html>
    
  3. 登出畫面按鈕

    @auth
      <form action="/logout" method="post">
         @csrf
         <button type="submit"> Log Out </button>
      </form>
    @else
    

上一篇
第 10 天:文件上傳與存儲
下一篇
第 12 天:用戶授權
系列文
後端小白自學 Laravel30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言